﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using EmbeddedLanguage.Attributes;
using EmbeddedLanguage.SubProcessors;

namespace EmbeddedLanguage
{
    public partial class ExpressionToJSProcessor
    {
        #region overridden Visits

        //public override Expression Visit(Expression node)
        //{
        //    return base.Visit(node);
        //}

        protected override Expression VisitBinary(BinaryExpression node)
        {

            string JSBinaryOperator = null;
            switch (node.NodeType)
            {
                case ExpressionType.Add:
                    JSBinaryOperator = " + ";
                    break;
                case ExpressionType.Equal:
                    JSBinaryOperator = " === ";
                    break;
                case ExpressionType.LessThan:
                    JSBinaryOperator = " < ";
                    break;
                case ExpressionType.Modulo:
                    JSBinaryOperator = " % ";
                    break;


                //default:
                //    throw new NotImplementedException();

            }
            if (JSBinaryOperator != null)
            {
                var nsIncoming = this.CurrentNestedString;
                var ns = new NestedString
                {
                    Parent = nsIncoming,
                    ChildSeparator = string.Empty,
                    ChildSeparatorFollowingLastElement = string.Empty,
                    ChildrenContinueOnLine = true,
                    Comment = "VisitBinary.BinaryExpressino",
                };
                this.CurrentNestedString = ns;
                this.Visit(node.Left);
                var nsOperator = new NestedString
                {
                    Parent = ns,
                    LHS = JSBinaryOperator,
                    Comment = "VisitBinary.Operator",
                };
                this.Visit(node.Right);
                this.CurrentNestedString = nsIncoming;
                return node;
            }
            return base.VisitBinary(node);

        }

        protected override Expression VisitConditional(ConditionalExpression node)
        {
            var nsIncoming = this.CurrentNestedString;
            this.RemoveReturnFromParent(nsIncoming);
            var nsIf = new NestedString
            {
                Comment = "VisitConditional.If",
                Parent = nsIncoming,
                LHS = "if(",
                RHS = ")",
            };
            this.CurrentNestedString = nsIf;
            this.Visit(node.Test);
            var nsThen = new NestedString
            {
                Parent = nsIncoming,
                Comment = "VisitiConditional.Then",
                Header = "{",
                LHS = "return ",
                RHS = ";",
                Footer = "}",
            };
            this.CurrentNestedString = nsThen;
            this.Visit(node.IfTrue);
            if (node.IfFalse != null)
            {
                var nsElse = new NestedString
                {
                    Parent = nsIncoming,
                    Comment = "VisitConditional.Else",
                    Header = "else{",
                    LHS = "return ",
                    RHS = ";",
                    Footer = "}",
                };
                this.CurrentNestedString = nsElse;
                this.Visit(node.IfFalse);
            }
            this.CurrentNestedString = nsIncoming;
            return node;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            string val = null;
            if (node.Value is bool)
            {
                val = node.Value.ToString().ToLower();
            }
            else if (node.Value is string)
            {
                val = "'" + node.Value + "'";
            }
            else
            {
                val = node.Value.ToString();
            }
            var ns = new NestedString
            {
                Parent = this.CurrentNestedString,
                Comment = "VisitConstant",
                LHS = val
            };
            return node;
            //return base.VisitConstant(node);
        }

        protected override Expression VisitInvocation(InvocationExpression node)
        {
            var member = node.Expression as MemberExpression;
            var mi = member.Member;
            var attrib = mi.GetAttribute<SubProcessorAttribute>();
            if (attrib != null)
            {
                var subProcessor = Activator.CreateInstance(attrib.JSSubProcessorType) as ExpressionToJSProcessor;
                if (subProcessor != null)
                {
                    this.TransferState(subProcessor);
                    return subProcessor.VisitMethodOrDelegateCall(node, null, member.Member, node.Arguments, true);
                }

            }
            return this.VisitMethodOrDelegateCall(node, node.Expression, member.Member, node.Arguments, true);

        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            var incomingNS = this.CurrentNestedString;
            NestedString ns = null;
            if (this.Function.ArgNames == null)
            {
                this.Function.ArgNames = this.GetNames(node.Parameters);
                //if (this.Function.SB == null) this.Function.SB = new StringBuilder();
                ns = new NestedString
                {
                    Parent = this.CurrentNestedString,
                    Comment = "VisitLambda.NoArgs",
                    LHS = "return ",
                    RHS = ";",
                    ChildSeparator = string.Empty,
                    ChildSeparatorFollowingLastElement = string.Empty,
                    ChildrenContinueOnLine = true,
                };

                var BodyNode = new NestedString
                {
                    Parent = ns,
                    Comment = "VisitLambda.Body",
                };
                this.CurrentNestedString = BodyNode;
                this.Visit(node.Body);
                this.CurrentNestedString = incomingNS;
                return node;
            }
            else
            {
                var args = this.GetNames(node.Parameters);
                ns = new NestedString
                {
                    Parent = this.CurrentNestedString,
                    Comment = "VisitLambda.Function",
                    Header = "function (" + string.Join(", ", args.ToArray()) + ") {",
                    Footer = "}",
                };
                var BodyNode = new NestedString
                {
                    Parent = ns,
                    Comment = "VisitLambda.2",
                    LHS = "return ",
                    RHS = ";",
                    ChildSeparator = string.Empty,
                    ChildSeparatorFollowingLastElement = string.Empty,
                    ChildrenContinueOnLine = true,
                };
                this.CurrentNestedString = BodyNode;
                this.Visit(node.Body);
                this.CurrentNestedString = incomingNS;
                return node;
            }

        }

        protected override Expression VisitMember(MemberExpression node)
        {
            var incoming = this.CurrentNestedString;
            var typ = node.Member.ReflectedType;
            string memberName = node.Member.Name;
            var tpl = new Tuple<Type, string>(typ, memberName);
            NestedString ns = null;
            if (Method2StaticFunctionMappingProcessor.MethodSubstitions.ContainsKey(tpl))
            {
                ns = new NestedString
                {
                    Parent = incoming,
                    LHS_Template = "{LHS}.do(" + Method2StaticFunctionMappingProcessor.MethodSubstitions[tpl],
                    Comment = Comment_VisitMember_do,
                    RHS = ")",
                    ChildSubstitutions = new Dictionary<string, int>
                    {
                        {"{LHS}", 0}
                    },
                    ChildSeparator = ",",
                    ChildSeparatorBeforeFirstElement = ","
                };
                //memberName = "do(" + Method2StaticFunctionMappingProcessor.MethodSubstitions[tpl];
            }
            else
            {
                ns = new NestedString
                {
                    Parent = incoming,
                    Comment = Comment_VisitMember_Start,
                    LHS_Template = "{LHS}",
                    RHS = "." + memberName,
                    ChildSubstitutions = new Dictionary<string, int>
                    {
                        {"{LHS}", 0}
                    }
                };
            }
            this.DetermineIfKaboose(ns);
            if (ns.IsKaboose)
            {
                ns.IsKabooseTransferrableToChild = true;
                ns.RHS += ";";
                ns.RemoveTrailingSemiColonBeforeRHS = true;
            }
            this.CurrentNestedString = ns;
            this.Visit(node.Expression);
            this.CurrentNestedString = incoming;
            return node;
            //return base.VisitMember(node);
        }

        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            var incoming = this.CurrentNestedString;
            #region Top
            
            var ns = new NestedString
            {
                Parent = this.CurrentNestedString,
                Comment = "VisitMemberInit.Top",
                //Header = "var me = {",
                Header = "{",
                RHS = "}",
                TreatRHSLikeBeginningOfFooter = true,
                ChildSeparator = ",",
                ChildSeparatorFollowingLastElement = string.Empty,
            };
            //this.KeyVariableName = "me";
            
            this.DetermineIfKaboose(ns);
            //if (ns.IsKaboose)
            //{
            //    if (ns.ReturningParent.LHS == "return ")
            //    {
            //        ns.ReturningParent.LHS = null;
            //        ns.Footer = "return me;";
            //    }
            //}

            
            #endregion
            #region set values
            foreach (MemberBinding binding in node.Bindings)
            {
                switch (binding.BindingType)
                {
                    case MemberBindingType.Assignment:
                        var nsAssignment = new NestedString
                        {
                            Parent = ns,
                            Comment = "VisitMemberInit.SetValues",
                            LHS = binding.Member.Name,
                            Waist = ":",
                            RHS_Template = "{RHS}",
                            ChildSubstitutions = new Dictionary<string, int>
                            {
                                {"{RHS}", 0}
                            }
                        };
                        this.CurrentNestedString = nsAssignment;

                        #region RHS
                        var ma = binding as MemberAssignment;
                        this.Visit(ma.Expression);

                        //var propInfo = binding.Member as PropertyInfo;

                        //if (propInfo != null)
                        //{
                        //    sb.Append(memberAssignment);
                        //}
                        //else
                        //{
                        //    var fieldInfo = binding.Member as FieldInfo;
                        //    if (fieldInfo != null)
                        //    {

                        //        if (fieldInfo.FieldType == typeof(string))
                        //        {
                        //            sb.Append("'" + memberAssignment + "'");
                        //        }
                        //        else
                        //        {
                        //            sb.Append(memberAssignment + "");
                        //            //throw new NotImplementedException();
                        //        }
                        //    }
                        //}
                        #endregion
                        break;
                    default:
                        throw new NotImplementedException();
                }

            }
            #endregion
            this.CurrentNestedString = incoming;
            return node;
        }



        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            //var member = node.Method as MemberExpression;
            var mi = node.Method;
            var attrib = mi.GetAttribute<SubProcessorAttribute>();
            if (attrib != null)
            {
                var subProcessor = Activator.CreateInstance(attrib.JSSubProcessorType) as ExpressionToJSProcessor;
                if (subProcessor != null)
                {
                    this.TransferState(subProcessor);
                    return subProcessor.VisitMethodOrDelegateCall(node, null, mi, node.Arguments, false);
                }

            }
            return this.VisitMethodOrDelegateCall(node, node.Object, node.Method, node.Arguments, false);
        }

        //protected override Expression VisitNew(NewExpression node)
        //{
        //    return base.VisitNew(node);
        //}

        protected override Expression VisitParameter(ParameterExpression node)
        {
            string node_name = node.Name;
            if (this.ParameterSubstitutions != null)
                while (this.ParameterSubstitutions.ContainsKey(node_name))
                {
                    node_name = this.ParameterSubstitutions[node_name];
                }
            var ns = new NestedString
            {
                Parent = this.CurrentNestedString,
                LHS = node_name,
                Comment = "VisitParameter.Parameter",
            };
            return base.VisitParameter(node);
        }


        #endregion
    }
}
